﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reflection;
using System.Runtime.Remoting.Messaging;
using Actsoft.Aspect;

namespace Actsoft.Invocation.Trace
{
    public class TraceProxy
    {
        private readonly ITraceListener listener;
    	private readonly string boundaryName;
    	private readonly ICollection<MethodBase> noTraceMethods = new Collection<MethodBase>();
    	private static int _silenceDepth;

    	private TraceProxy(ITraceListener listener, string boundaryName)
        {
        	this.listener = listener;
        	this.boundaryName = boundaryName;
        }

    	private static T CreateProxy<T>(T source, TraceProxy proxy)
    	{
    		proxy.TraverseTypeForAttributes(typeof(T));
            return new RealProxyBasedFactory().CreateProxy(source, proxy.OnInvocation);
        }

    	private void TraverseTypeForAttributes(Type type)
    	{
    		foreach (var m in type.GetMethods().Where(m => m.GetCustomAttributes(typeof(NoTraceAttribute), true).Any()))
    		{
    			noTraceMethods.Add(m);
    		}
    	}

    	public static T MakeFor<T>(T source, ITraceListener listener)
        {
			return CreateProxy(source, new TraceProxy(listener, null));
        }

        public static T MakeFor<T>(T source, string boundaryName, ITraceListener listener)
        {
        	return listener == null ? source : CreateProxy(source, new TraceProxy(listener, boundaryName));
        }

		public IMethodReturnMessage OnInvocation(IMethodCallMessage msg, RemotingInvocation target)
		{
			bool silent = noTraceMethods.Contains(msg.MethodBase);
			if (silent)
				_silenceDepth++;

			try
			{
				if (_silenceDepth > 0)
					return target(msg, null);

				long id = listener.OnMethodEntry(msg, boundaryName);

				IMethodReturnMessage ret;
				try
				{
					ret = target(msg, null);
				}
				catch (Exception e)
				{
					ret = new ReturnMessage(e, msg);
					listener.OnMethodLeave(ret, id);
					throw;
				}
				listener.OnMethodLeave(ret, id);
				return ret;
			}finally
			{
				if (silent)
					_silenceDepth--;
			}
		}
    }
}
